/*
 * This file is distributed as part of the MariaDB Corporation MaxScale.  It is free
 * software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation,
 * version 2.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright MariaDB Corporation Ab 2013-2014
 */

/*
 * MySQL Protocol common routines for client to gateway and gateway to backend
 *
 * Revision History
 * Date         Who                     Description
 * 17/06/2013   Massimiliano Pinto      Common MySQL protocol routines
 * 02/06/2013   Massimiliano Pinto      MySQL connect asynchronous phases
 * 04/09/2013   Massimiliano Pinto      Added dcb NULL assert in mysql_send_custom_error
 * 12/09/2013   Massimiliano Pinto      Added checks in gw_decode_mysql_server_handshake and
 *                                      gw_read_backend_handshake
 * 10/02/2014   Massimiliano Pinto      Added MySQL Authentication with user@host
 * 10/09/2014   Massimiliano Pinto      Added MySQL Authentication option enabling localhost
 *                                      match with any host (wildcard %)
 *                                      Backend server configuration may differ so default is 0,
 *                                      don't match and an explicit
 *                                      localhost entry should be added for the selected user
 *                                      in the backends.
 *                                      Setting to 1 allow localhost (127.0.0.1 or socket) to
 *                                      match the any host grant via
 *                                      user@%
 * 29/09/2014   Massimiliano Pinto      Added Mysql user@host authentication with wildcard in IPv4 hosts:
 *                                      x.y.z.%, x.y.%.%, x.%.%.%
 * 03/10/2014   Massimiliano Pinto      Added netmask for wildcard in IPv4 hosts.
 * 24/10/2014   Massimiliano Pinto      Added Mysql user@host @db authentication support
 * 10/11/2014   Massimiliano Pinto      Charset at connect is passed to backend during authentication
 * 07/07/2015   Martin Brampton         Fix problem recognising null password
 * 07/02/2016   Martin Brampton         Remove authentication functions to mysql_auth.c
 *
 */

#include <gw.h>
#include "mysql_client_server_protocol.h"
#include <skygw_types.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <netinet/tcp.h>

/* The following can be compared using memcmp to detect a null password */
uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]="";

extern int gw_read_backend_event(DCB* dcb);
extern int gw_write_backend_event(DCB *dcb);
extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
extern int gw_error_backend_event(DCB *dcb);

static server_command_t* server_command_init(server_command_t* srvcmd, mysql_server_cmd_t cmd);


/**
 * Creates MySQL protocol structure
 *
 * @param dcb *          Must be non-NULL.
 * @param fd
 *
 * @return
 *
 *
 * @details Protocol structure does not have fd because dcb is not
 * connected yet.
 *
 */
MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd)
{
    MySQLProtocol* p;

    p = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol));
    ss_dassert(p != NULL);

    if (p == NULL)
    {
        int eno = errno;
        errno = 0;
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("%lu [mysql_init_protocol] MySQL protocol init failed : "
                  "memory allocation due error  %d, %s.",
                  pthread_self(),
                  eno,
                  strerror_r(eno, errbuf, sizeof(errbuf)));
        goto return_p;
    }
    p->protocol_state = MYSQL_PROTOCOL_ALLOC;
    p->protocol_auth_state = MYSQL_ALLOC;
    p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED;
    p->protocol_command.scom_nresponse_packets = 0;
    p->protocol_command.scom_nbytes_to_read = 0;
#if defined(SS_DEBUG)
    p->protocol_chk_top = CHK_NUM_PROTOCOL;
    p->protocol_chk_tail = CHK_NUM_PROTOCOL;
#endif
    /*< Assign fd with protocol */
    p->fd = fd;
    p->owner_dcb = dcb;
    p->protocol_state = MYSQL_PROTOCOL_ACTIVE;
    CHK_PROTOCOL(p);
return_p:
    return p;
}


/**
 * mysql_protocol_done
 *
 * free protocol allocations.
 *
 * @param dcb owner DCB
 *
 */
void mysql_protocol_done(DCB* dcb)
{
    MySQLProtocol* p;
    server_command_t* scmd;
    server_command_t* scmd2;

    p = (MySQLProtocol *)dcb->protocol;

    spinlock_acquire(&p->protocol_lock);

    if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE)
    {
        goto retblock;
    }
    scmd = p->protocol_cmd_history;

    while (scmd != NULL)
    {
        scmd2 = scmd->scom_next;
        free(scmd);
        scmd = scmd2;
    }
    p->protocol_state = MYSQL_PROTOCOL_DONE;

retblock:
    spinlock_release(&p->protocol_lock);
}


/**
 * Read the backend server MySQL handshake
 *
 * @param conn  MySQL protocol structure
 * @return 0 on success, 1 on failure
 */
int gw_read_backend_handshake(MySQLProtocol *conn)
{
    GWBUF *head = NULL;
    DCB *dcb = conn->owner_dcb;
    int n = -1;
    uint8_t *payload = NULL;
    int h_len = 0;
    int  success = 0;
    int packet_len = 0;

    if ((n = dcb_read(dcb, &head, 0)) != -1)
    {
        dcb->last_read = hkheartbeat;

        if (head)
        {
            payload = GWBUF_DATA(head);
            h_len = gwbuf_length(head);

            /**
             * The mysql packets content starts at byte fifth
             * just return with less bytes
             */

            if (h_len <= 4)
            {
                /* log error this exit point */
                conn->protocol_auth_state = MYSQL_HANDSHAKE_FAILED;
                MXS_DEBUG("%lu [gw_read_backend_handshake] after "
                          "dcb_read, fd %d, "
                          "state = MYSQL_HANDSHAKE_FAILED.",
                          pthread_self(),
                          dcb->fd);

                return 1;
            }

            if (payload[4] == 0xff)
            {
                size_t len = MYSQL_GET_PACKET_LEN(payload);
                uint16_t errcode = MYSQL_GET_ERRCODE(payload);
                char* bufstr = strndup(&((char *)payload)[7], len - 3);

                conn->protocol_auth_state = MYSQL_HANDSHAKE_FAILED;

                MXS_DEBUG("%lu [gw_receive_backend_auth] Invalid "
                          "authentication message from backend dcb %p "
                          "fd %d, ptr[4] = %d, error code %d, msg %s.",
                          pthread_self(),
                          dcb,
                          dcb->fd,
                          payload[4],
                          errcode,
                          bufstr);

                MXS_ERROR("Invalid authentication message "
                          "from backend. Error code: %d, Msg : %s",
                          errcode,
                          bufstr);

                /**
                 * If ER_HOST_IS_BLOCKED is found
                 * the related server is put in maintenace mode
                 * This will avoid filling the error log.
                 */

                if (errcode == 1129)
                {
                    MXS_ERROR("Server %s has been put into maintenance mode due "
                              "to the server blocking connections from MaxScale. "
                              "Run 'mysqladmin -h %s -P %d flush-hosts' on this "
                              "server before taking this server out of maintenance "
                              "mode.",
                              dcb->server->unique_name,
                              dcb->server->name,
                              dcb->server->port);

                    server_set_status(dcb->server, SERVER_MAINT);
                }

                free(bufstr);
            }
            //get mysql packet size, 3 bytes
            packet_len = gw_mysql_get_byte3(payload);

            if (h_len < (packet_len + 4))
            {
                /*
                 * data in buffer less than expected in the
                 * packet. Log error this exit point
                 */

                conn->protocol_auth_state = MYSQL_HANDSHAKE_FAILED;

                MXS_DEBUG("%lu [gw_read_backend_handshake] after "
                          "gw_mysql_get_byte3, fd %d, "
                          "state = MYSQL_HANDSHAKE_FAILED.",
                          pthread_self(),
                          dcb->fd);

                return 1;
            }

            // skip the 4 bytes header
            payload += 4;

            //Now decode mysql handshake
            success = gw_decode_mysql_server_handshake(conn, payload);

            if (success < 0)
            {
                /* MySQL handshake has not been properly decoded
                 * we cannot continue
                 * log error this exit point
                 */
                conn->protocol_auth_state = MYSQL_HANDSHAKE_FAILED;

                MXS_DEBUG("%lu [gw_read_backend_handshake] after "
                          "gw_decode_mysql_server_handshake, fd %d, "
                          "state = MYSQL_HANDSHAKE_FAILED.",
                          pthread_self(),
                          conn->owner_dcb->fd);
                while ((head = gwbuf_consume(head, GWBUF_LENGTH(head))))
                {
                    ;
                }
                return 1;
            }

            conn->protocol_auth_state = MYSQL_AUTH_SENT;

            // consume all the data here
            head = gwbuf_consume(head, GWBUF_LENGTH(head));

            return 0;
        }
    }

    // Nothing done here, log error this
    return 1;
}

/**
 * gw_decode_mysql_server_handshake
 *
 * Decode mysql server handshake
 *
 * @param conn The MySQLProtocol structure
 * @param payload The bytes just read from the net
 * @return 0 on success, < 0 on failure
 *
 */
int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload)
{
    uint8_t *server_version_end = NULL;
    uint16_t mysql_server_capabilities_one = 0;
    uint16_t mysql_server_capabilities_two = 0;
    unsigned long tid = 0;
    uint8_t scramble_data_1[GW_SCRAMBLE_LENGTH_323] = "";
    uint8_t scramble_data_2[GW_MYSQL_SCRAMBLE_SIZE - GW_SCRAMBLE_LENGTH_323] = "";
    uint8_t capab_ptr[4] = "";
    int scramble_len = 0;
    uint8_t scramble[GW_MYSQL_SCRAMBLE_SIZE] = "";
    int protocol_version = 0;

    protocol_version = payload[0];

    if (protocol_version != GW_MYSQL_PROTOCOL_VERSION)
    {
        return -1;
    }

    payload++;

    // Get server version (string)
    server_version_end = (uint8_t *) gw_strend((char*) payload);

    payload = server_version_end + 1;

    // get ThreadID: 4 bytes
    tid = gw_mysql_get_byte4(payload);
    memcpy(&conn->tid, &tid, 4);

    payload += 4;

    // scramble_part 1
    memcpy(scramble_data_1, payload, GW_SCRAMBLE_LENGTH_323);
    payload += GW_SCRAMBLE_LENGTH_323;

    // 1 filler
    payload++;

    mysql_server_capabilities_one = gw_mysql_get_byte2(payload);

    //Get capabilities_part 1 (2 bytes) + 1 language + 2 server_status
    payload += 5;

    mysql_server_capabilities_two = gw_mysql_get_byte2(payload);

    memcpy(&capab_ptr, &mysql_server_capabilities_one, 2);

    // get capabilities part 2 (2 bytes)
    memcpy(&(capab_ptr[2]), &mysql_server_capabilities_two, 2);

    // 2 bytes shift
    payload += 2;

    // get scramble len
    if (payload[0] > 0)
    {
        scramble_len = payload[0] -1;
        ss_dassert(scramble_len > GW_SCRAMBLE_LENGTH_323);
        ss_dassert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE);

        if ((scramble_len < GW_SCRAMBLE_LENGTH_323) ||
            scramble_len > GW_MYSQL_SCRAMBLE_SIZE)
        {
            /* log this */
            return -2;
        }
    }
    else
    {
        scramble_len = GW_MYSQL_SCRAMBLE_SIZE;
    }
    // skip 10 zero bytes
    payload += 11;

    // copy the second part of the scramble
    memcpy(scramble_data_2, payload, scramble_len - GW_SCRAMBLE_LENGTH_323);

    memcpy(scramble, scramble_data_1, GW_SCRAMBLE_LENGTH_323);
    memcpy(scramble + GW_SCRAMBLE_LENGTH_323, scramble_data_2, scramble_len - GW_SCRAMBLE_LENGTH_323);

    // full 20 bytes scramble is ready
    memcpy(conn->scramble, scramble, GW_MYSQL_SCRAMBLE_SIZE);

    return 0;
}


/**
 * Receive the MySQL authentication packet from backend, packet # is 2
 *
 * @param protocol The MySQL protocol structure
 * @return -1 in case of failure, 0 if there was nothing to read, 1 if read
 * was successful.
 */
int gw_receive_backend_auth(MySQLProtocol *protocol)
{
    int n = -1;
    GWBUF *head = NULL;
    DCB *dcb = protocol->owner_dcb;
    uint8_t *ptr = NULL;
    int rc = 0;

    n = dcb_read(dcb, &head, 0);

    dcb->last_read = hkheartbeat;

    /*<
     * Read didn't fail and there is enough data for mysql packet.
     */
    if (n != -1 &&
        head != NULL &&
        GWBUF_LENGTH(head) >= 5)
    {
        ptr = GWBUF_DATA(head);
        /*<
         * 5th byte is 0x0 if successful.
         */
        if (ptr[4] == 0x00)
        {
            rc = 1;
        }
        else if (ptr[4] == 0xff)
        {
            size_t len = MYSQL_GET_PACKET_LEN(ptr);
            char* err = strndup(&((char *)ptr)[8], 5);
            char* bufstr = strndup(&((char *)ptr)[13], len - 4 - 5);

            MXS_DEBUG("%lu [gw_receive_backend_auth] Invalid "
                      "authentication message from backend dcb %p "
                      "fd %d, ptr[4] = %d, error %s, msg %s.",
                      pthread_self(),
                      dcb,
                      dcb->fd,
                      ptr[4],
                      err,
                      bufstr);

            MXS_ERROR("Invalid authentication message "
                      "from backend. Error : %s, Msg : %s",
                      err,
                      bufstr);

            free(bufstr);
            free(err);
            rc = -1;
        }
        else
        {
            MXS_DEBUG("%lu [gw_receive_backend_auth] Invalid "
                      "authentication message from backend dcb %p "
                      "fd %d, ptr[4] = %d",
                      pthread_self(),
                      dcb,
                      dcb->fd,
                      ptr[4]);

            MXS_ERROR("Invalid authentication message "
                      "from backend. Packet type : %d",
                      ptr[4]);
        }
        /*<
         * Remove data from buffer.
         */
        while ((head = gwbuf_consume(head, GWBUF_LENGTH(head))) != NULL)
        {
            ;
        }
    }
    else if (n == 0)
    {
        /*<
         * This is considered as success because call didn't fail,
         * although no bytes was read.
         */
        rc = 0;
        MXS_DEBUG("%lu [gw_receive_backend_auth] Read zero bytes from "
                  "backend dcb %p fd %d in state %s. n %d, head %p, len %ld",
                  pthread_self(),
                  dcb,
                  dcb->fd,
                  STRDCBSTATE(dcb->state),
                  n,
                  head,
                  (head == NULL) ? 0 : GWBUF_LENGTH(head));
    }
    else
    {
        ss_dassert(n < 0 && head == NULL);
        rc = -1;
        MXS_DEBUG("%lu [gw_receive_backend_auth] Reading from backend dcb %p "
                  "fd %d in state %s failed. n %d, head %p, len %ld",
                  pthread_self(),
                  dcb,
                  dcb->fd,
                  STRDCBSTATE(dcb->state),
                  n,
                  head,
                  (head == NULL) ? 0 : GWBUF_LENGTH(head));
    }

    return rc;
}

/**
 * Write MySQL authentication packet to backend server
 *
 * @param conn  MySQL protocol structure
 * @param dbname The selected database
 * @param user The selected user
 * @param passwd The SHA1(real_password): Note real_password is unknown
 * @return 0 on success, 1 on failure
 */
int gw_send_authentication_to_backend(char *dbname,
                                      char *user,
                                      uint8_t *passwd,
                                      MySQLProtocol *conn)
{
    int compress = 0;
    int rv;
    uint8_t *payload = NULL;
    uint8_t *payload_start = NULL;
    long bytes;
    uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
    uint8_t client_capabilities[4];
    uint32_t server_capabilities = 0;
    uint32_t final_capabilities  = 0;
    char dbpass[MYSQL_USER_MAXLEN + 1]="";
    GWBUF *buffer;
    DCB *dcb;

    char *curr_db = NULL;
    uint8_t *curr_passwd = NULL;
    unsigned int charset;

    /**
     * If session is stopping return with error.
     */
    if (conn->owner_dcb->session == NULL ||
        (conn->owner_dcb->session->state != SESSION_STATE_READY &&
         conn->owner_dcb->session->state != SESSION_STATE_ROUTER_READY))
    {
        return 1;
    }

    if (strlen(dbname))
    {
        curr_db = dbname;
    }

    if (memcmp(passwd, null_client_sha1, MYSQL_SCRAMBLE_LEN))
    {
        curr_passwd = passwd;
    }

    dcb = conn->owner_dcb;
    final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);

    /** Copy client's flags to backend but with the known capabilities mask */
    final_capabilities |= (conn->client_capabilities & GW_MYSQL_CAPABILITIES_CLIENT);

    /* get charset the client sent and use it for connection auth */
    charset = conn->charset;

    if (compress)
    {
        final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
        fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
    }

    if (curr_passwd != NULL)
    {
        uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
        uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
        uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";

        // hash1 is the function input, SHA1(real_password)
        memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE);

        // hash2 is the SHA1(input data), where input_data = SHA1(real_password)
        gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);

        // dbpass is the HEX form of SHA1(SHA1(real_password))
        gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);

        // new_sha is the SHA1(CONCAT(scramble, hash2)
        gw_sha1_2_str(conn->scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha);

        // compute the xor in client_scramble
        gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE);

    }

    if (curr_db == NULL)
    {
        // without db
        final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
    }
    else
    {
        final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
    }

    final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;

    gw_mysql_set_byte4(client_capabilities, final_capabilities);

    // Protocol MySQL HandshakeResponse for CLIENT_PROTOCOL_41
    // 4 bytes capabilities + 4 bytes max packet size + 1 byte charset + 23 '\0' bytes
    // 4 + 4 + 1 + 23  = 32
    bytes = 32;

    bytes += strlen(user);
    // the NULL
    bytes++;

    // next will be + 1 (scramble_len) + 20 (fixed_scramble) + 1 (user NULL term) + 1 (db NULL term)

    if (curr_passwd != NULL)
    {
        bytes++;
        bytes += GW_MYSQL_SCRAMBLE_SIZE;
    }
    else
    {
        bytes++;
    }

    if (curr_db != NULL)
    {
        bytes += strlen(curr_db);
        bytes++;
    }

    bytes += strlen("mysql_native_password");
    bytes++;

    // the packet header
    bytes += 4;

    // allocating the GWBUF
    buffer = gwbuf_alloc(bytes);
    payload = GWBUF_DATA(buffer);

    // clearing data
    memset(payload, '\0', bytes);

    // save the start pointer
    payload_start = payload;

    // set packet # = 1
    payload[3] = '\x01';
    payload += 4;

    // set client capabilities
    memcpy(payload, client_capabilities, 4);

    // set now the max-packet size
    payload += 4;
    gw_mysql_set_byte4(payload, 16777216);

    // set the charset
    payload += 4;
    *payload = charset;

    payload++;

    // 23 bytes of 0
    payload += 23;

    // 4 + 4 + 4 + 1 + 23 = 36, this includes the 4 bytes packet header

    memcpy(payload, user, strlen(user));
    payload += strlen(user);
    payload++;

    if (curr_passwd != NULL)
    {
        // set the auth-length
        *payload = GW_MYSQL_SCRAMBLE_SIZE;
        payload++;

        //copy the 20 bytes scramble data after packet_buffer + 36 + user + NULL + 1 (byte of auth-length)
        memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);

        payload += GW_MYSQL_SCRAMBLE_SIZE;

    }
    else
    {
        // skip the auth-length and write a NULL
        payload++;
    }

    // if the db is not NULL append it
    if (curr_db != NULL)
    {
        memcpy(payload, curr_db, strlen(curr_db));
        payload += strlen(curr_db);
        payload++;
    }

    memcpy(payload,
           "mysql_native_password",
           strlen("mysql_native_password"));
    payload += strlen("mysql_native_password");
    payload++;

    // put here the paylod size: bytes to write - 4 bytes packet header
    gw_mysql_set_byte3(payload_start, (bytes - 4));

    rv = dcb_write(dcb, buffer);

    if (rv == 0)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/**
 * gw_do_connect_to_backend
 *
 * This routine creates socket and connects to a backend server.
 * Connect it non-blocking operation. If connect fails, socket is closed.
 *
 * @param host The host to connect to
 * @param port The host TCP/IP port
 * @param *fd where connected fd is copied
 * @return 0/1 on success and -1 on failure
 * If succesful, fd has file descriptor to socket which is connected to
 * backend server. In failure, fd == -1 and socket is closed.
 *
 */
int gw_do_connect_to_backend(char *host, int port, int *fd)
{
    struct sockaddr_in serv_addr;
    int rv;
    int so = 0;
    int bufsize;

    memset(&serv_addr, 0, sizeof serv_addr);
    serv_addr.sin_family = AF_INET;
    so = socket(AF_INET,SOCK_STREAM,0);

    if (so < 0)
    {
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("Establishing connection to backend server "
                  "%s:%d failed.\n\t\t             Socket creation failed "
                  "due %d, %s.",
                  host,
                  port,
                  errno,
                  strerror_r(errno, errbuf, sizeof(errbuf)));
        rv = -1;
        goto return_rv;
    }
    /* prepare for connect */
    setipaddress(&serv_addr.sin_addr, host);
    serv_addr.sin_port = htons(port);
    bufsize = GW_BACKEND_SO_SNDBUF;

    if (setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0)
    {
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("Failed to set socket options "
                  "%s:%d failed.\n\t\t             Socket configuration failed "
                  "due %d, %s.",
                  host,
                  port,
                  errno,
                  strerror_r(errno, errbuf, sizeof(errbuf)));
        rv = -1;
        /** Close socket */
        goto close_so;
    }
    bufsize = GW_BACKEND_SO_RCVBUF;

    if (setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0)
    {
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("Failed to set socket options "
                  "%s:%d failed.\n\t\t             Socket configuration failed "
                  "due %d, %s.",
                  host,
                  port,
                  errno,
                  strerror_r(errno, errbuf, sizeof(errbuf)));
        rv = -1;
        /** Close socket */
        goto close_so;
    }

    int one = 1;
    if (setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0)
    {
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("Failed to set socket options "
                  "%s:%d failed.\n\t\t             Socket configuration failed "
                  "due %d, %s.",
                  host,
                  port,
                  errno,
                  strerror_r(errno, errbuf, sizeof(errbuf)));
        rv = -1;
        /** Close socket */
        goto close_so;
    }

    /* set socket to as non-blocking here */
    setnonblocking(so);
    rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    if (rv != 0)
    {
        if (errno == EINPROGRESS)
        {
            rv = 1;
        }
        else
        {
            char errbuf[STRERROR_BUFLEN];
            MXS_ERROR("Failed to connect backend server %s:%d, "
                      "due %d, %s.",
                      host,
                      port,
                      errno,
                      strerror_r(errno, errbuf, sizeof(errbuf)));
            /** Close socket */
            goto close_so;
        }
    }
    *fd = so;
    MXS_DEBUG("%lu [gw_do_connect_to_backend] Connected to backend server "
              "%s:%d, fd %d.",
              pthread_self(), host, port, so);
#if defined(FAKE_CODE)
    conn_open[so] = true;
#endif /* FAKE_CODE */

return_rv:
    return rv;

close_so:
    /*< Close newly created socket. */
    if (close(so) != 0)
    {
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("Failed to close socket %d due %d, %s.",
                  so,
                  errno,
                  strerror_r(errno, errbuf, sizeof(errbuf)));
    }
    goto return_rv;
}

/**
 * Return a string representation of a MySQL protocol state.
 *
 * @param state The protocol state
 * @return String representation of the state
 *
 */
const char* gw_mysql_protocol_state2string (int state)
{
    switch(state)
    {
    case MYSQL_ALLOC:
        return "MySQL Protocl struct allocated";
    case MYSQL_PENDING_CONNECT:
        return "MySQL Backend socket PENDING connect";
    case MYSQL_CONNECTED:
        return "MySQL Backend socket CONNECTED";
    case MYSQL_AUTH_SENT:
        return "MySQL Authentication handshake has been sent";
    case MYSQL_AUTH_RECV:
        return "MySQL Received user, password, db and capabilities";
    case MYSQL_AUTH_FAILED:
        return "MySQL Authentication failed";
    case MYSQL_IDLE:
        return "MySQL authentication is succesfully done.";
    case MYSQL_AUTH_SSL_REQ:
        return "MYSQL_AUTH_SSL_REQ";
    case MYSQL_AUTH_SSL_HANDSHAKE_DONE:
        return "MYSQL_AUTH_SSL_HANDSHAKE_DONE";
    case MYSQL_AUTH_SSL_HANDSHAKE_FAILED:
        return "MYSQL_AUTH_SSL_HANDSHAKE_FAILED";
    case MYSQL_AUTH_SSL_HANDSHAKE_ONGOING:
        return "MYSQL_AUTH_SSL_HANDSHAKE_ONGOING";
    default:
        return "MySQL (unknown protocol state)";
    }
}

GWBUF* mysql_create_com_quit(GWBUF* bufparam,
                             int packet_number)
{
    uint8_t* data;
    GWBUF* buf;

    if (bufparam == NULL)
    {
        buf = gwbuf_alloc(COM_QUIT_PACKET_SIZE);
    }
    else
    {
        buf = bufparam;
    }

    if (buf == NULL)
    {
        return 0;
    }
    ss_dassert(GWBUF_LENGTH(buf) == COM_QUIT_PACKET_SIZE);

    data = GWBUF_DATA(buf);

    *data++ = 0x1;
    *data++ = 0x0;
    *data++ = 0x0;
    *data++ = packet_number;
    *data   = 0x1;

    return buf;
}

int mysql_send_com_quit(DCB*   dcb,
                        int    packet_number,
                        GWBUF* bufparam)
{
    GWBUF *buf;
    int nbytes = 0;

    CHK_DCB(dcb);
    ss_dassert(packet_number <= 255);

    if (dcb == NULL || dcb->state == DCB_STATE_ZOMBIE)
    {
        return 0;
    }
    if (bufparam == NULL)
    {
        buf = mysql_create_com_quit(NULL, packet_number);
    }
    else
    {
        buf = bufparam;
    }

    if (buf == NULL)
    {
        return 0;
    }
    nbytes = dcb->func.write(dcb, buf);

    return nbytes;
}


GWBUF* mysql_create_custom_error(int         packet_number,
                                 int         affected_rows,
                                 const char* msg)
{
    uint8_t* outbuf = NULL;
    uint32_t mysql_payload_size = 0;
    uint8_t mysql_packet_header[4];
    uint8_t* mysql_payload = NULL;
    uint8_t field_count = 0;
    uint8_t mysql_err[2];
    uint8_t mysql_statemsg[6];
    unsigned int mysql_errno = 0;
    const char* mysql_error_msg = NULL;
    const char* mysql_state = NULL;

    GWBUF* errbuf = NULL;

    mysql_errno = 2003;
    mysql_error_msg = "An errorr occurred ...";
    mysql_state = "HY000";

    field_count = 0xff;
    gw_mysql_set_byte2(mysql_err, mysql_errno);
    mysql_statemsg[0]='#';
    memcpy(mysql_statemsg + 1, mysql_state, 5);

    if (msg != NULL)
    {
        mysql_error_msg = msg;
    }

    mysql_payload_size =
        sizeof(field_count) +
        sizeof(mysql_err) +
        sizeof(mysql_statemsg) +
        strlen(mysql_error_msg);

    /** allocate memory for packet header + payload */
    errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size);
    ss_dassert(errbuf != NULL);

    if (errbuf == NULL)
    {
        return 0;
    }
    outbuf = GWBUF_DATA(errbuf);

    /** write packet header and packet number */
    gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
    mysql_packet_header[3] = packet_number;

    /** write header */
    memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));

    mysql_payload = outbuf + sizeof(mysql_packet_header);

    /** write field */
    memcpy(mysql_payload, &field_count, sizeof(field_count));
    mysql_payload = mysql_payload + sizeof(field_count);

    /** write errno */
    memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
    mysql_payload = mysql_payload + sizeof(mysql_err);

    /** write sqlstate */
    memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
    mysql_payload = mysql_payload + sizeof(mysql_statemsg);

    /** write error message */
    memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));

    return errbuf;
}
/**
 * mysql_send_custom_error
 *
 * Send a MySQL protocol Generic ERR message, to the dcb
 * Note the errno and state are still fixed now
 *
 * @param dcb Owner_Dcb Control Block for the connection to which the OK is sent
 * @param packet_number
 * @param in_affected_rows
 * @param mysql_message
 * @return 1 Non-zero if data was sent
 *
 */
int mysql_send_custom_error(DCB       *dcb,
                            int        packet_number,
                            int        in_affected_rows,
                            const char *mysql_message)
{
    GWBUF* buf;

    buf = mysql_create_custom_error(packet_number, in_affected_rows, mysql_message);

    return dcb->func.write(dcb, buf);
}

/**
 * Create COM_CHANGE_USER packet and store it to GWBUF
 *
 * @param mses          MySQL session
 * @param protocol      protocol structure of the backend
 *
 * @return GWBUF buffer consisting of COM_CHANGE_USER packet
 *
 * @note the function doesn't fail
 */
GWBUF* gw_create_change_user_packet(MYSQL_session*  mses,
                                    MySQLProtocol*  protocol)
{
    char* db;
    char* user;
    uint8_t* pwd;
    GWBUF* buffer;
    int compress = 0;
    uint8_t* payload = NULL;
    uint8_t* payload_start = NULL;
    long bytes;
    uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
    uint32_t server_capabilities = 0;
    uint32_t final_capabilities  = 0;
    char dbpass[MYSQL_USER_MAXLEN + 1]="";
    char* curr_db = NULL;
    uint8_t* curr_passwd = NULL;
    unsigned int charset;

    db = mses->db;
    user = mses->user;
    pwd = mses->client_sha1;

    if (strlen(db) > 0)
    {
        curr_db = db;
    }

    if (memcmp(pwd, null_client_sha1, MYSQL_SCRAMBLE_LEN))
    {
        curr_passwd = pwd;
    }
    final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);

    /** Copy client's flags to backend */
    final_capabilities |= protocol->client_capabilities;

    /* get charset the client sent and use it for connection auth */
    charset = protocol->charset;

    if (compress)
    {
        final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
        fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
    }

    if (curr_passwd != NULL)
    {
        uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
        uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
        uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";

        /** hash1 is the function input, SHA1(real_password) */
        memcpy(hash1, pwd, GW_MYSQL_SCRAMBLE_SIZE);

        /**
         * hash2 is the SHA1(input data), where
         * input_data = SHA1(real_password)
         */
        gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);

        /** dbpass is the HEX form of SHA1(SHA1(real_password)) */
        gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);

        /** new_sha is the SHA1(CONCAT(scramble, hash2) */
        gw_sha1_2_str(protocol->scramble,
                      GW_MYSQL_SCRAMBLE_SIZE,
                      hash2,
                      GW_MYSQL_SCRAMBLE_SIZE,
                      new_sha);

        /** compute the xor in client_scramble */
        gw_str_xor(client_scramble,
                   new_sha, hash1,
                   GW_MYSQL_SCRAMBLE_SIZE);
    }
    if (curr_db == NULL)
    {
        final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
    }
    else
    {
        final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
    }
    final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
    /**
     * Protocol MySQL COM_CHANGE_USER for CLIENT_PROTOCOL_41
     * 1 byte COMMAND
     */
    bytes = 1;

    /** add the user and a terminating char */
    bytes += strlen(user);
    bytes++;
    /**
     * next will be + 1 (scramble_len) + 20 (fixed_scramble) +
     * (db + NULL term) + 2 bytes charset
     */
    if (curr_passwd != NULL)
    {
        bytes += GW_MYSQL_SCRAMBLE_SIZE;
    }
    /** 1 byte for scramble_len */
    bytes++;
    /** db name and terminating char */
    if (curr_db != NULL)
    {
        bytes += strlen(curr_db);
    }
    bytes++;

    /** the charset */
    bytes += 2;
    bytes += strlen("mysql_native_password");
    bytes++;

    /** the packet header */
    bytes += 4;

    buffer = gwbuf_alloc(bytes);
    /**
     * Set correct type to GWBUF so that it will be handled like session
     * commands
     */
    buffer->gwbuf_type = GWBUF_TYPE_MYSQL|GWBUF_TYPE_SINGLE_STMT|GWBUF_TYPE_SESCMD;
    payload = GWBUF_DATA(buffer);
    memset(payload, '\0', bytes);
    payload_start = payload;

    /** set packet number to 0 */
    payload[3] = 0x00;
    payload += 4;

    /** set the command COM_CHANGE_USER 0x11 */
    payload[0] = 0x11;
    payload++;
    memcpy(payload, user, strlen(user));
    payload += strlen(user);
    payload++;

    if (curr_passwd != NULL)
    {
        /** set the auth-length */
        *payload = GW_MYSQL_SCRAMBLE_SIZE;
        payload++;
        /**
         * copy the 20 bytes scramble data after
         * packet_buffer + 36 + user + NULL + 1 (byte of auth-length)
         */
        memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);
        payload += GW_MYSQL_SCRAMBLE_SIZE;
    }
    else
    {
        /** skip the auth-length and write a NULL */
        payload++;
    }
    /** if the db is not NULL append it */
    if (curr_db != NULL)
    {
        memcpy(payload, curr_db, strlen(curr_db));
        payload += strlen(curr_db);
    }
    payload++;
    /** set the charset, 2 bytes */
    *payload = charset;
    payload++;
    *payload = '\x00';
    payload++;
    memcpy(payload, "mysql_native_password", strlen("mysql_native_password"));
    payload += strlen("mysql_native_password");
    payload++;
    /** put here the paylod size: bytes to write - 4 bytes packet header */
    gw_mysql_set_byte3(payload_start, (bytes-4));

    return buffer;
}

/**
 * Write a MySQL CHANGE_USER packet to backend server
 *
 * @param conn  MySQL protocol structure
 * @param dbname The selected database
 * @param user The selected user
 * @param passwd The SHA1(real_password)
 * @return 1 on success, 0 on failure
 */
int gw_send_change_user_to_backend(char          *dbname,
                                   char          *user,
                                   uint8_t       *passwd,
                                   MySQLProtocol *conn)
{
    GWBUF *buffer;
    int rc;
    MYSQL_session*  mses;

    mses = (MYSQL_session*)conn->owner_dcb->session->client_dcb->data;
    buffer = gw_create_change_user_packet(mses, conn);
    rc = conn->owner_dcb->func.write(conn->owner_dcb, buffer);

    if (rc != 0)
    {
        rc = 1;
    }
    return rc;
}


/**
 * gw_find_mysql_user_password_sha1
 *
 * The routine fetches an user from the MaxScale users' table
 * The users' table is dcb->service->users or a different one specified with void *repository
 * The user lookup uses username,host and db name (if passed in connection or change user)
 *
 * If found the HEX password, representing sha1(sha1(password)), is converted in binary data and
 * copied into gateway_password
 *
 * @param username              The user to look for
 * @param gateway_password      The related SHA1(SHA1(password)), the pointer must be preallocated
 * @param dcb                   Current DCB
 * @return 1 if user is not found or 0 if the user exists
 *
 */

int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, DCB *dcb)
{
    SERVICE *service = NULL;
    struct sockaddr_in *client;
    char *user_password = NULL;
    MYSQL_USER_HOST key;
    MYSQL_session *client_data = NULL;

    client_data = (MYSQL_session *) dcb->data;
    service = (SERVICE *) dcb->service;
    client = (struct sockaddr_in *) &dcb->ipv4;

    key.user = username;
    memcpy(&key.ipv4, client, sizeof(struct sockaddr_in));
    key.netmask = 32;
    key.resource = client_data->db;
    if (strlen(dcb->remote) < MYSQL_HOST_MAXLEN)
    {
        strcpy(key.hostname, dcb->remote);
    }

    MXS_DEBUG("%lu [MySQL Client Auth], checking user [%s@%s]%s%s",
              pthread_self(),
              key.user,
              dcb->remote,
              key.resource != NULL ?" db: " :"",
              key.resource != NULL ?key.resource :"");

    /* look for user@current_ipv4 now */
    user_password = mysql_users_fetch(service->users, &key);

    if (!user_password)
    {
        /* The user is not authenticated @ current IPv4 */

        while (1)
        {
            /*
             * (1) Check for localhost first: 127.0.0.1 (IPv4 only)
             */

            if ((key.ipv4.sin_addr.s_addr == 0x0100007F) &&
                !dcb->service->localhost_match_wildcard_host)
            {
                /* Skip the wildcard check and return 1 */
                break;
            }

            /*
             * (2) check for possible IPv4 class C,B,A networks
             */

            /* Class C check */
            key.ipv4.sin_addr.s_addr &= 0x00FFFFFF;
            key.netmask -= 8;

            user_password = mysql_users_fetch(service->users, &key);

            if (user_password)
            {
                break;
            }

            /* Class B check */
            key.ipv4.sin_addr.s_addr &= 0x0000FFFF;
            key.netmask -= 8;

            user_password = mysql_users_fetch(service->users, &key);

            if (user_password)
            {
                break;
            }

            /* Class A check */
            key.ipv4.sin_addr.s_addr &= 0x000000FF;
            key.netmask -= 8;

            user_password = mysql_users_fetch(service->users, &key);

            if (user_password)
            {
                break;
            }

            /*
             * (3) Continue check for wildcard host, user@%
             */

            memset(&key.ipv4, 0, sizeof(struct sockaddr_in));
            key.netmask = 0;

            MXS_DEBUG("%lu [MySQL Client Auth], checking user [%s@%s] with "
                      "wildcard host [%%]",
                      pthread_self(),
                      key.user,
                      dcb->remote);

            user_password = mysql_users_fetch(service->users, &key);

            if (user_password)
            {
                break;
            }

            if (!user_password)
            {
                /*
                 * user@% not found.
                 */

                MXS_DEBUG("%lu [MySQL Client Auth], user [%s@%s] not existent",
                          pthread_self(),
                          key.user,
                          dcb->remote);

                MXS_INFO("Authentication Failed: user [%s@%s] not found.",
                         key.user,
                         dcb->remote);
                break;
            }

        }
    }

    /* If user@host has been found we get the the password in binary format*/
    if (user_password)
    {
        /*
         * Convert the hex data (40 bytes) to binary (20 bytes).
         * The gateway_password represents the SHA1(SHA1(real_password)).
         * Please note: the real_password is unknown and SHA1(real_password) is unknown as well
         */
        int passwd_len=strlen(user_password);
        if (passwd_len)
        {
            passwd_len = (passwd_len <= (SHA_DIGEST_LENGTH * 2)) ? passwd_len : (SHA_DIGEST_LENGTH * 2);
            gw_hex2bin(gateway_password, user_password, passwd_len);
        }

        return 0;
    } else {
        return 1;
    }
}

/**
 * mysql_send_auth_error
 *
 * Send a MySQL protocol ERR message, for gateway authentication error to the dcb
 *
 * @param dcb descriptor Control Block for the connection to which the OK is sent
 * @param packet_number
 * @param in_affected_rows
 * @param mysql_message
 * @return packet length
 *
 */
int mysql_send_auth_error(DCB        *dcb,
                          int        packet_number,
                          int        in_affected_rows,
                          const char *mysql_message)
{
    uint8_t *outbuf = NULL;
    uint32_t mysql_payload_size = 0;
    uint8_t mysql_packet_header[4];
    uint8_t *mysql_payload = NULL;
    uint8_t field_count = 0;
    uint8_t mysql_err[2];
    uint8_t mysql_statemsg[6];
    unsigned int mysql_errno = 0;
    const char *mysql_error_msg = NULL;
    const char *mysql_state = NULL;

    GWBUF *buf;

    if (dcb->state != DCB_STATE_POLLING)
    {
        MXS_DEBUG("%lu [mysql_send_auth_error] dcb %p is in a state %s, "
                  "and it is not in epoll set anymore. Skip error sending.",
                  pthread_self(),
                  dcb,
                  STRDCBSTATE(dcb->state));
        return 0;
    }
    mysql_errno = 1045;
    mysql_error_msg = "Access denied!";
    mysql_state = "28000";

    field_count = 0xff;
    gw_mysql_set_byte2(mysql_err, mysql_errno);
    mysql_statemsg[0]='#';
    memcpy(mysql_statemsg + 1, mysql_state, 5);

    if (mysql_message != NULL)
    {
        mysql_error_msg = mysql_message;
    }

    mysql_payload_size =
        sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg);

    // allocate memory for packet header + payload
    if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
    {
        return 0;
    }
    outbuf = GWBUF_DATA(buf);

    // write packet header with packet number
    gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
    mysql_packet_header[3] = packet_number;

    // write header
    memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));

    mysql_payload = outbuf + sizeof(mysql_packet_header);

    // write field
    memcpy(mysql_payload, &field_count, sizeof(field_count));
    mysql_payload = mysql_payload + sizeof(field_count);

    // write errno
    memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
    mysql_payload = mysql_payload + sizeof(mysql_err);

    // write sqlstate
    memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
    mysql_payload = mysql_payload + sizeof(mysql_statemsg);

    // write err messg
    memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));

    // writing data in the Client buffer queue
    dcb->func.write(dcb, buf);

    return sizeof(mysql_packet_header) + mysql_payload_size;
}


/**
 * Buffer contains at least one of the following:
 * complete [complete] [partial] mysql packet
 *
 * @param p_readbuf     Address of read buffer pointer
 *
 * @return pointer to gwbuf containing a complete packet or
 *   NULL if no complete packet was found.
 */
GWBUF* gw_MySQL_get_next_packet(GWBUF** p_readbuf)
{
    GWBUF* packetbuf;
    GWBUF* readbuf;
    size_t buflen;
    size_t packetlen;
    size_t totalbuflen;
    uint8_t* data;
    size_t nbytes_copied = 0;
    uint8_t* target;

    readbuf = *p_readbuf;

    if (readbuf == NULL)
    {
        packetbuf = NULL;
        goto return_packetbuf;
    }
    CHK_GWBUF(readbuf);

    if (GWBUF_EMPTY(readbuf))
    {
        packetbuf = NULL;
        goto return_packetbuf;
    }
    totalbuflen = gwbuf_length(readbuf);
    data = (uint8_t *)GWBUF_DATA((readbuf));
    packetlen = MYSQL_GET_PACKET_LEN(data) + 4;

    /** packet is incomplete */
    if (packetlen > totalbuflen)
    {
        packetbuf = NULL;
        goto return_packetbuf;
    }

    packetbuf = gwbuf_alloc(packetlen);
    target = GWBUF_DATA(packetbuf);
    packetbuf->gwbuf_type = readbuf->gwbuf_type; /*< Copy the type too */
    /**
     * Copy first MySQL packet to packetbuf and leave posible other
     * packets to read buffer.
     */
    while (nbytes_copied < packetlen && totalbuflen > 0)
    {
        uint8_t* src = GWBUF_DATA((*p_readbuf));
        size_t   bytestocopy;

        buflen = GWBUF_LENGTH((*p_readbuf));
        bytestocopy = MIN(buflen, packetlen - nbytes_copied);

        memcpy(target + nbytes_copied, src, bytestocopy);
        *p_readbuf = gwbuf_consume((*p_readbuf), bytestocopy);
        totalbuflen = gwbuf_length((*p_readbuf));
        nbytes_copied += bytestocopy;
    }
    ss_dassert(buflen == 0 || nbytes_copied == packetlen);

return_packetbuf:
    return packetbuf;
}

/**
 * Move <npackets> from buffer pointed to by <*p_readbuf>.
 */
GWBUF* gw_MySQL_get_packets(GWBUF** p_srcbuf,
                            int*    npackets)
{
    GWBUF* packetbuf;
    GWBUF* targetbuf = NULL;

    while (*npackets > 0 && (packetbuf = gw_MySQL_get_next_packet(p_srcbuf)) != NULL)
    {
        targetbuf = gwbuf_append(targetbuf, packetbuf);
        *npackets -= 1;
    }
    ss_dassert(*npackets < 128);
    ss_dassert(*npackets >= 0);

    return targetbuf;
}


static server_command_t* server_command_init(server_command_t* srvcmd,
                                             mysql_server_cmd_t cmd)
{
    server_command_t* c;

    if (srvcmd != NULL)
    {
        c = srvcmd;
    }
    else
    {
        c = (server_command_t *)malloc(sizeof(server_command_t));
    }
    c->scom_cmd = cmd;
    c->scom_nresponse_packets = -1;
    c->scom_nbytes_to_read = 0;
    c->scom_next = NULL;

    return c;
}

static server_command_t* server_command_copy(server_command_t* srvcmd)
{
    server_command_t* c = (server_command_t *)malloc(sizeof(server_command_t));
    *c = *srvcmd;

    return c;
}

#define MAX_CMD_HISTORY 10

void protocol_archive_srv_command(MySQLProtocol* p)
{
    server_command_t* s1;
    server_command_t* h1;
    int len = 0;

    CHK_PROTOCOL(p);

    spinlock_acquire(&p->protocol_lock);

    if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE)
    {
        goto retblock;
    }

    s1 = &p->protocol_command;
#if defined(EXTRA_SS_DEBUG)
    MXS_INFO("Move command %s from fd %d to command history.",
             STRPACKETTYPE(s1->scom_cmd),
             p->owner_dcb->fd);
#endif
    /** Copy to history list */
    if ((h1 = p->protocol_cmd_history) == NULL)
    {
        p->protocol_cmd_history = server_command_copy(s1);
    }
    else /*< scan and count history commands */
    {
        len = 1;

        while (h1->scom_next != NULL)
        {
            h1 = h1->scom_next;
            len += 1;
        }
        h1->scom_next = server_command_copy(s1);
    }

    /** Keep history limits, remove oldest */
    if (len > MAX_CMD_HISTORY)
    {
        server_command_t* c = p->protocol_cmd_history;
        p->protocol_cmd_history = p->protocol_cmd_history->scom_next;
        free(c);
    }

    /** Remove from command list */
    if (s1->scom_next == NULL)
    {
        p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED;
    }
    else
    {
        p->protocol_command = *(s1->scom_next);
        free(s1->scom_next);
    }

retblock:
    spinlock_release(&p->protocol_lock);
    CHK_PROTOCOL(p);
}


/**
 * If router expects to get separate, complete statements, add MySQL command
 * to MySQLProtocol structure. It is removed when response has arrived.
 */
void protocol_add_srv_command(MySQLProtocol*     p,
                              mysql_server_cmd_t cmd)
{
#if defined(EXTRA_SS_DEBUG)
    server_command_t* c;
#endif
    spinlock_acquire(&p->protocol_lock);

    if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE)
    {
        goto retblock;
    }
    /** this is the only server command in protocol */
    if (p->protocol_command.scom_cmd == MYSQL_COM_UNDEFINED)
    {
        /** write into structure */
        server_command_init(&p->protocol_command, cmd);
    }
    else
    {
        /** add to the end of list */
        p->protocol_command.scom_next = server_command_init(NULL, cmd);
    }
#if defined(EXTRA_SS_DEBUG)
    MXS_INFO("Added command %s to fd %d.",
             STRPACKETTYPE(cmd),
             p->owner_dcb->fd);

    c = &p->protocol_command;

    while (c != NULL && c->scom_cmd != MYSQL_COM_UNDEFINED)
    {
        MXS_INFO("fd %d : %d %s",
                 p->owner_dcb->fd,
                 c->scom_cmd,
                 STRPACKETTYPE(c->scom_cmd));
        c = c->scom_next;
    }
#endif
retblock:
    spinlock_release(&p->protocol_lock);
}


/**
 * If router processes separate statements, every stmt has corresponding MySQL
 * command stored in MySQLProtocol structure.
 *
 * Remove current (=oldest) command.
 */
void protocol_remove_srv_command(MySQLProtocol* p)
{
    server_command_t* s;
    spinlock_acquire(&p->protocol_lock);
    s = &p->protocol_command;
#if defined(EXTRA_SS_DEBUG)
    MXS_INFO("Removed command %s from fd %d.",
             STRPACKETTYPE(s->scom_cmd),
             p->owner_dcb->fd);
#endif
    if (s->scom_next == NULL)
    {
        p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED;
    }
    else
    {
        p->protocol_command = *(s->scom_next);
        free(s->scom_next);
    }

    spinlock_release(&p->protocol_lock);
}

mysql_server_cmd_t protocol_get_srv_command(MySQLProtocol* p,
                                            bool           removep)
{
    mysql_server_cmd_t cmd;

    cmd = p->protocol_command.scom_cmd;

    if (removep)
    {
        protocol_remove_srv_command(p);
    }
    MXS_DEBUG("%lu [protocol_get_srv_command] Read command %s for fd %d.",
              pthread_self(),
              STRPACKETTYPE(cmd),
              p->owner_dcb->fd);
    return cmd;
}


/**
 * Examine command type and the readbuf. Conclude response
 * packet count from the command type or from the first packet
 * content.
 * Fails if read buffer doesn't include enough data to read the
 * packet length.
 */
void init_response_status(GWBUF*             buf,
                          mysql_server_cmd_t cmd,
                          int*               npackets,
                          ssize_t*           nbytes_left)
{
    uint8_t* packet;
    int nparam;
    int nattr;
    uint8_t* data;

    ss_dassert(gwbuf_length(buf) >= 3);

    data = (uint8_t *)buf->start;

    if (data[4] == 0xff) /*< error */
    {
        *npackets = 1;
    }
    else
    {
        switch (cmd)
        {
        case MYSQL_COM_STMT_PREPARE:
            packet = (uint8_t *)GWBUF_DATA(buf);
            /** ok + nparam + eof + nattr + eof */
            nparam = MYSQL_GET_STMTOK_NPARAM(packet);
            nattr  = MYSQL_GET_STMTOK_NATTR(packet);

            *npackets = 1 + nparam + MIN(1, nparam) +
                nattr + MIN(nattr, 1);
            break;

        case MYSQL_COM_QUIT:
        case MYSQL_COM_STMT_SEND_LONG_DATA:
        case MYSQL_COM_STMT_CLOSE:
            *npackets = 0; /*< these don't reply anything */
            break;

        default:
            /**
             * assume that other session commands respond
             * OK or ERR
             */
            *npackets = 1;
            break;
        }
    }
    *nbytes_left = MYSQL_GET_PACKET_LEN(data) + MYSQL_HEADER_LEN;
    /**
     * There is at least one complete packet in the buffer so buffer is bigger
     * than packet
     */
    ss_dassert(*nbytes_left > 0);
    ss_dassert(*npackets > 0);
}



/**
 * Read how many packets are left from current response and how many bytes there
 * is still to be read from the current packet.
 */
bool protocol_get_response_status(MySQLProtocol* p,
                                  int* npackets,
                                  ssize_t* nbytes)
{
    bool succp;

    CHK_PROTOCOL(p);

    spinlock_acquire(&p->protocol_lock);
    *npackets = p->protocol_command.scom_nresponse_packets;
    *nbytes   = (ssize_t)p->protocol_command.scom_nbytes_to_read;
    spinlock_release(&p->protocol_lock);

    if (*npackets < 0 && *nbytes == 0)
    {
        succp = false;
    }
    else
    {
        succp = true;
    }

    return succp;
}

void protocol_set_response_status(MySQLProtocol* p,
                                  int npackets_left,
                                  ssize_t nbytes)
{
    CHK_PROTOCOL(p);

    spinlock_acquire(&p->protocol_lock);

    p->protocol_command.scom_nbytes_to_read = nbytes;
    ss_dassert(p->protocol_command.scom_nbytes_to_read >= 0);

    p->protocol_command.scom_nresponse_packets = npackets_left;

    spinlock_release(&p->protocol_lock);
}

char* create_auth_failed_msg(GWBUF*readbuf,
                             char* hostaddr,
                             uint8_t* sha1)
{
    char* errstr;
    char* uname=(char *)GWBUF_DATA(readbuf) + 5;
    const char* ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";

    /** -4 comes from 2X'%s' minus terminating char */
    errstr = (char *)malloc(strlen(uname) + strlen(ferrstr) + strlen(hostaddr) + strlen("YES") - 6 + 1);

    if (errstr != NULL)
    {
        sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
    }

    return errstr;
}

/**
 * Create a message error string to send via MySQL ERR packet.
 *
 * @param       username        the MySQL user
 * @param       hostaddr        the client IP
 * @param       sha1            authentication scramble data
 * @param       db              the MySQL db to connect to
 *
 * @return      Pointer to the allocated string or NULL on failure
 */
char *create_auth_fail_str(char *username,
                           char *hostaddr,
                           char *sha1,
                           char *db,
                           int errcode)
{
    char* errstr;
    const char* ferrstr;
    int db_len;

    if (db != NULL)
    {
        db_len = strlen(db);
    }
    else
    {
        db_len = 0;
    }

    if (db_len > 0)
    {
        ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'";
    }
    else if (errcode == MYSQL_FAILED_AUTH_SSL)
    {
        ferrstr = "Access without SSL denied";
    }
    else
    {
        ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";
    }
    errstr = (char *)malloc(strlen(username) + strlen(ferrstr) +
                            strlen(hostaddr) + strlen("YES") - 6 +
                            db_len + ((db_len > 0) ? (strlen(" to database ") +2) : 0) + 1);

    if (errstr == NULL)
    {
        char errbuf[STRERROR_BUFLEN];
        MXS_ERROR("Memory allocation failed due to %s.",
                  strerror_r(errno, errbuf, sizeof(errbuf)));
        goto retblock;
    }

    if (db_len > 0)
    {
        sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db);
    }
    else if (errcode == MYSQL_FAILED_AUTH_SSL)
    {
        sprintf(errstr, "%s", ferrstr);
    }
    else
    {
        sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
    }

retblock:
    return errstr;
}
